'require dom';
'require fs';
-let callPackagelist = rpc.declare({
+const callPackagelist = rpc.declare({
object: 'rpc-sys',
method: 'packagelist',
});
-let callSystemBoard = rpc.declare({
+const callSystemBoard = rpc.declare({
object: 'system',
method: 'board',
});
-let callUpgradeStart = rpc.declare({
+const callUpgradeStart = rpc.declare({
object: 'rpc-sys',
method: 'upgrade_start',
params: ['keep'],
return view.extend({
steps: {
- init: [10, _('Received build request')],
- download_imagebuilder: [20, _('Downloading ImageBuilder archive')],
- unpack_imagebuilder: [40, _('Setting Up ImageBuilder')],
- calculate_packages_hash: [60, _('Validate package selection')],
- building_image: [80, _('Generating firmware image')],
+ init: [ 0, _('Received build request')],
+ container_setup: [ 10, _('Setting up ImageBuilder')],
+ validate_revision: [ 20, _('Validating revision')],
+ validate_manifest: [ 30, _('Validating package selection')],
+ calculate_packages_hash: [ 40, _('Calculating package hash')],
+ building_image: [ 50, _('Generating firmware image')],
+ signing_images: [ 95, _('Signing images')],
+ done: [100, _('Completed generating firmware image')],
+ failed: [100, _('Failed to generate firmware image')],
+
+ /* Obsolete status values, retained for backward compatibility. */
+ download_imagebuilder: [ 20, _('Downloading ImageBuilder archive')],
+ unpack_imagebuilder: [ 40, _('Setting Up ImageBuilder')],
},
- data: {
- url: '',
- revision: '',
- advanced_mode: 0,
- rebuilder: [],
- sha256_unsigned: '',
- },
+ request_hash: '',
+ sha256_unsigned: '',
+
+ applyPackageChanges: async function(package_info) {
+ let { url, target, version, packages } = package_info;
+
+ const overview_url = `${url}/api/v1/overview`;
+ const revision_url = `${url}/api/v1/revision/${version}/${target}`;
- firmware: {
- profile: '',
- target: '',
- version: '',
- packages: [],
- diff_packages: true,
- filesystem: '',
+ let changes, target_revision;
+
+ await Promise.all([
+ request.get(overview_url).then(
+ (response) => {
+ let json = response.json();
+ changes = json.branches[get_branch(version)].package_changes;
+ },
+ (failed) => {
+ ui.addNotification(null, E('p', _(`Get overview failed ${failed}`)));
+ }
+ ),
+ request.get(revision_url).then(
+ (response) => {
+ target_revision = get_revision_count(response.json().revision);
+ },
+ (failed) => {
+ ui.addNotification(null, E('p', _(`Get revision failed ${failed}`)));
+ }
+ ),
+ ]);
+
+ for (const change of changes) {
+ let idx = packages.indexOf(change.source);
+ if (idx >= 0 && change.revision <= target_revision) {
+ if (change.target)
+ packages[idx] = change.target;
+ else
+ packages.splice(idx, 1);
+ }
+ }
+ return packages;
},
- selectImage: function (images) {
- let firmware = this.firmware;
- let data = this.data;
+ selectImage: function (images, data, firmware) {
var filesystemFilter = function(e) {
return (e.filesystem == firmware.filesystem);
}
var typeFilter = function(e) {
- if (firmware.target.indexOf("x86") != -1) {
- // x86 images can be combined-efi (EFI) or combined (BIOS)
+ let efi_targets = ['armsr', 'loongarch', 'x86'];
+ let efi_capable = efi_targets.some((tgt) => firmware.target.startsWith(tgt));
+ if (efi_capable) {
if (data.efi) {
return (e.type == 'combined-efi');
} else {
return images.filter(filesystemFilter).filter(typeFilter)[0];
},
- handle200: function (response) {
+ handle200: function (response, content, data, firmware) {
response = response.json();
- let image = this.selectImage(response.images);
+ let image = this.selectImage(response.images, data, firmware);
if (image.name != undefined) {
- this.data.sha256_unsigned = image.sha256_unsigned;
- let sysupgrade_url = `${this.data.url}/store/${response.bin_dir}/${image.name}`;
+ this.sha256_unsigned = image.sha256_unsigned;
+ let sysupgrade_url = `${data.url}/store/${response.bin_dir}/${image.name}`;
let keep = E('input', { type: 'checkbox' });
keep.checked = true;
image.sha256,
];
- if (this.data.advanced_mode == 1) {
+ if (data.advanced_mode == 1) {
fields.push(
_('Profile'),
response.id,
'',
E('a', { href: sysupgrade_url }, _('Download firmware image'))
);
- if (this.data.rebuilder) {
+ if (data.rebuilder) {
fields.push(_('Rebuilds'), E('div', { id: 'rebuilder_status' }));
}
];
ui.showModal(_('Successfully created firmware image'), modal_body);
- if (this.data.rebuilder) {
- this.handleRebuilder();
+ if (data.rebuilder) {
+ this.handleRebuilder(content, data, firmware);
}
}
},
handle202: function (response) {
response = response.json();
- this.data.request_hash = response.request_hash;
+ this.request_hash = response.request_hash;
if ('queue_position' in response) {
ui.showModal(_('Queued...'), [
}
},
- handleError: function (response) {
+ handleError: function (response, data, firmware) {
response = response.json();
+ const request_data = {
+ ...data,
+ request_hash: this.request_hash,
+ sha256_unsigned: this.sha256_unsigned,
+ ...firmware
+ };
let body = [
E('p', {}, _('Server response: %s').format(response.detail)),
E(
_('Please report the error message and request')
),
E('p', {}, _('Request Data:')),
- E('pre', {}, JSON.stringify({ ...this.data, ...this.firmware }, null, 4)),
+ E('pre', {}, JSON.stringify({ ...request_data }, null, 4)),
];
if (response.stdout) {
ui.showModal(_('Error building the firmware image'), body);
},
- handleRequest: function (server, main) {
+ handleRequest: function (server, main, content, data, firmware) {
let request_url = `${server}/api/v1/build`;
let method = 'POST';
- let content = this.firmware;
+ let local_content = content;
/**
* If `request_hash` is available use a GET request instead of
* sending the entire object.
*/
- if (this.data.request_hash && main == true) {
- request_url += `/${this.data.request_hash}`;
- content = {};
+ if (this.request_hash && main == true) {
+ request_url += `/${this.request_hash}`;
+ local_content = {};
method = 'GET';
}
request
- .request(request_url, { method: method, content: content })
+ .request(request_url, { method: method, content: local_content })
.then((response) => {
switch (response.status) {
case 202:
case 200:
if (main == true) {
poll.remove(this.pollFn);
- this.handle200(response);
+ this.handle200(response, content, data, firmware);
} else {
poll.remove(this.rebuilder_polls[server]);
response = response.json();
let view = document.getElementById(server);
- let image = this.selectImage(response.images);
- if (image.sha256_unsigned == this.data.sha256_unsigned) {
+ let image = this.selectImage(response.images, data, firmware);
+ if (image.sha256_unsigned == this.sha256_unsigned) {
view.innerText = '✅ %s'.format(server);
} else {
view.innerHTML = `⚠️ ${server} (<a href="${server}/store/${
case 500: // build failed
if (main == true) {
poll.remove(this.pollFn);
- this.handleError(response);
+ this.handleError(response, data, firmware);
break;
} else {
poll.remove(this.rebuilder_polls[server]);
});
},
- handleRebuilder: function () {
+ handleRebuilder: function (content, data, firmware) {
this.rebuilder_polls = {};
- for (let rebuilder of this.data.rebuilder) {
+ for (let rebuilder of data.rebuilder) {
this.rebuilder_polls[rebuilder] = L.bind(
this.handleRequest,
this,
rebuilder,
- false
+ false,
+ content,
+ data,
+ firmware
);
poll.add(this.rebuilder_polls[rebuilder], 5);
document.getElementById(
});
},
- handleCheck: function () {
- let { url, revision } = this.data;
- let { version, target } = this.firmware;
+ handleCheck: function (data, firmware) {
+ this.request_hash = '';
+ let { url, revision, advanced_mode, branch } = data;
+ let { version, target, profile, packages } = firmware;
let candidates = [];
- let request_url = `${url}/api/overview`;
- if (version.endsWith('SNAPSHOT')) {
- request_url = `${url}/api/v1/revision/${version}/${target}`;
- }
+
+ const endpoint = version.endsWith('SNAPSHOT') ? `revision/${version}/${target}` : 'overview';
+ const request_url = `${url}/api/v1/${endpoint}`;
ui.showModal(_('Searching...'), [
E(
} else {
const latest = response.json().latest;
+ // ensure order: newest to oldest release
+ latest.sort().reverse();
+
for (let remote_version of latest) {
let remote_branch = get_branch(remote_version);
}
// skip branch upgrades outside the advanced mode
- if (
- this.data.branch != remote_branch &&
- this.data.advanced_mode == 0
- ) {
+ if (branch != remote_branch && advanced_mode == 0) {
continue;
}
candidates.unshift([remote_version, null]);
// don't offer branches older than the current
- if (this.data.branch == remote_branch) {
+ if (branch == remote_branch) {
break;
}
}
}
// allow to re-install running firmware in advanced mode
- if (this.data.advanced_mode == 1) {
+ if (advanced_mode == 1) {
candidates.unshift([version, revision]);
}
let mapdata = {
request: {
- profile: this.firmware.profile,
+ profile,
version: candidates[0][0],
- packages: Object.keys(this.firmware.packages).sort(),
+ packages: Object.keys(packages).sort(),
},
};
}
}
- if (this.data.advanced_mode == 1) {
+ if (advanced_mode == 1) {
o = s.option(form.Value, 'profile', _('Board Name / Profile'));
o = s.option(form.DynamicList, 'packages', _('Packages'));
}
E(
'p',
_('Currently running: %s - %s').format(
- this.firmware.version,
- this.data.revision
+ version,
+ revision
)
),
form_rendered,
class: 'btn cbi-button cbi-button-positive important',
click: ui.createHandlerFn(this, function () {
map.save().then(() => {
- this.firmware.packages = mapdata.request.packages;
- this.firmware.version = mapdata.request.version;
- this.firmware.profile = mapdata.request.profile;
- this.pollFn = L.bind(function () {
- this.handleRequest(this.data.url, true);
- }, this);
- poll.add(this.pollFn, 5);
- poll.start();
+ this.applyPackageChanges({
+ url,
+ target,
+ version: mapdata.request.version,
+ packages: mapdata.request.packages,
+ }).then((packages) => {
+ const content = {
+ ...firmware,
+ packages: packages,
+ version: mapdata.request.version,
+ profile: mapdata.request.profile
+ };
+ this.pollFn = L.bind(function () {
+ this.handleRequest(url, true, content, data, firmware);
+ }, this);
+ poll.add(this.pollFn, 5);
+ poll.start();
+ });
});
}),
},
});
},
- load: function () {
- return Promise.all([
+ load: async function () {
+ const promises = await Promise.all([
L.resolveDefault(callPackagelist(), {}),
L.resolveDefault(callSystemBoard(), {}),
L.resolveDefault(fs.stat('/sys/firmware/efi'), null),
uci.load('attendedsysupgrade'),
]);
+ const data = {
+ url: uci.get_first('attendedsysupgrade', 'server', 'url'),
+ branch: get_branch(promises[1].release.version),
+ revision: promises[1].release.revision,
+ efi: promises[2],
+ advanced_mode: uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0,
+ rebuilder: uci.get_first('attendedsysupgrade', 'server', 'rebuilder')
+ };
+ const firmware = {
+ client: 'luci/' + promises[0].packages['luci-app-attendedsysupgrade'],
+ packages: promises[0].packages,
+ profile: promises[1].board_name,
+ target: promises[1].release.target,
+ version: promises[1].release.version,
+ diff_packages: true,
+ filesystem: promises[1].rootfs_type
+ };
+ return [data, firmware];
},
render: function (response) {
- this.firmware.client =
- 'luci/' + response[0].packages['luci-app-attendedsysupgrade'];
- this.firmware.packages = response[0].packages;
-
- this.firmware.profile = response[1].board_name;
- this.firmware.target = response[1].release.target;
- this.firmware.version = response[1].release.version;
- this.data.branch = get_branch(response[1].release.version);
- this.firmware.filesystem = response[1].rootfs_type;
- this.data.revision = response[1].release.revision;
-
- this.data.efi = response[2];
-
- this.data.url = uci.get_first('attendedsysupgrade', 'server', 'url');
- this.data.advanced_mode =
- uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
- this.data.rebuilder = uci.get_first(
- 'attendedsysupgrade',
- 'server',
- 'rebuilder'
- );
+ const data = response[0];
+ const firmware = response[1];
return E('p', [
E('h2', _('Attended Sysupgrade')),
E(
'p',
_(
- 'The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.'
+ 'The attended sysupgrade service allows to upgrade vanilla and custom firmware images easily.'
)
),
E(
E(
'p',
_('Currently running: %s - %s').format(
- this.firmware.version,
- this.data.revision
+ firmware.version,
+ data.revision
)
),
E(
'button',
{
class: 'btn cbi-button cbi-button-positive important',
- click: ui.createHandlerFn(this, this.handleCheck),
+ click: ui.createHandlerFn(this, this.handleCheck, data, firmware),
},
_('Search for firmware upgrade')
),